//link all opengl libraries
#include <GL/glew.h>
//windows management
#include <GLFW/glfw3.h>
//matrices
#include <glm/ext/matrix_clip_space.hpp> 
#include <glm/ext/matrix_transform.hpp>
#include <glm/geometric.hpp>
#include <glm/trigonometric.hpp>
// i/o console
#include <iostream>
//files
#include <fstream>
//strings
#include <string>
#include <sstream>
//math library
#include <math.h>
//images
#include <SOIL/SOIL.h>
//matrices
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
//other includes
#include "shaderBuilder/shaderBuilder.h"    //parse abd build shaders
#include "TextureLoader/textureLoader.h"    //Load textures and bind them
#include "camera/camera.h"                  //Camera movement and management

const unsigned int WIDTH = 800;
const unsigned int HEIGHT = 600; 

camera view;

float yaw, pitch, roll;

// Deltatime
GLfloat deltaTime = 0.0f;	// Time between current frame and last frame
GLfloat lastFrame = 0.0f; 

void processInput(GLFWwindow *window);
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);

int main(){

std::cout << "Starting GLFW context, OpenGL 3.3" << std::endl;
    // Init GLFW
    if(!glfwInit()){
        std::cout << "Failed to initialize GLFW" <<std::endl;
        glfwTerminate();
        return -1;
    }

    // Set all the required options for GLFW
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    // Create a GLFWwindow object that we can use for GLFW's functions
    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "Identity", nullptr, nullptr);
    glfwMakeContextCurrent(window);

    // Set the required callback functions
    glfwSetKeyCallback(window, key_callback);
    glfwSetCursorPosCallback(window, mouse_callback);

    // Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions
    glewExperimental = GL_TRUE;
    // Initialize GLEW to setup the OpenGL Function pointers
    glewInit();

    // Define the viewport dimensions
    //glfwGetFramebufferSize(window, &width, &height);  
    glViewport(0, 0, WIDTH, HEIGHT);


    // Build and compile our shader program
    // Vertex shader

    shaderBuilder shader( "../res/shaders/basic.shader");

//    glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
//    glm::mat4 trans;
//    trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
//    vec = trans * vec;
//    std::cout << vec.x << vec.y << vec.z << std::endl;


float vertices[] = {
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
     0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

    -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
};


glm::vec3 cubePosition[] = {
    glm::vec3( 0.0f, 0.0f, 0.0f),
    glm::vec3( 2.0f, 5.0f, -15.0f),
    glm::vec3( 1.5f, 2.2f, 2.5f),
    glm::vec3( 3.8f, -2.0f, 12.3f),
    glm::vec3( 2.4f, -0.4f, -3.5f),
    glm::vec3( 0.0f, 0.0f, 0.0f),
    glm::vec3( 0.0f, 0.0f, 0.0f),
    glm::vec3( 0.0f, 0.0f, 0.0f),
    glm::vec3( 0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 0.0f, 0.0f), };
//    unsigned int indices[] = {  // Note that we start from 0!
//        0, 1, 3,  // First Triangle
//        1, 2, 3   // Second Triangle
//    };


    unsigned int VBO, VAO; //here was EBO
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    //glGenBuffers(1, &EBO);
    // Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointer(s).
    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    //glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    //position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    //color attribute
//    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3*(sizeof(GLfloat))));
//    glEnableVertexAttribArray(1);
    //texCoord attribute
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3*(sizeof(float))));
    glEnableVertexAttribArray(1);

//    glBindBuffer(GL_ARRAY_BUFFER, 0); // Note that this is allowed, the call to glVertexAttribPointer registered VBO as the currently bound vertex buffer object so afterwards we can safely unbind

    //glBindVertexArray(0); // Unbind VAO (it's always a good thing to unbind any buffer/array to prevent strange bugs), remember: do NOT unbind the EBO, keep it bound to this VAO


    // Uncommenting this call will result in wireframe polygons.
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    textureLoader texture1("../res/textures/container.jpg");
    textureLoader texture2("../res/textures/awesomeface.png");

    shader.use();

    view.setPos(glm::vec3(0.0f, 0.0f, 3.0f));
    view.setDirection(glm::vec3(0.0f, 0.0f, -1.0f));
    view.setUp(glm::vec3(0.0f, 1.0f, 0.0f));
    view.setYaw(-90.0f);
    view.setPitch(0.0f);
    view.setLastx(WIDTH  / 2.0);
    view.setLasty(HEIGHT / 2.0);


    // Game loop
    while (!glfwWindowShouldClose(window))
    {
        // Check if any events have been activiated (key pressed, mouse moved etc.) and call corresponding response functions
        glfwPollEvents();
        processInput(window);
        view.do_movement();

        // Render
        // Clear the colorbuffer and depth buffer

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        //active textures and set uniforms 

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture1.getTextureID());
        shader.setInt("texture1", 0);

        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2.getTextureID());
        shader.setInt("texture2", 1);

        //active program

        shader.use();


        //we need to create a local, model, view and a  projection matrix in order to create a clip space
        //for our 3D objects 
        //model will rotate the cubePosition
        //view will move cube in z coord remember view is a right handed axis
        glm::mat4 model = glm::mat4(1.0f);
        glm::mat4 projection = glm::mat4(1.0f);
        //model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));
        //view.setView(glm::translate(view.getView(), glm::vec3(0.0f, 0.0f, -3.0f)));


        // Note: currently we set the projection matrix each frame, but since the projection matrix rarely changes it's often best practice to set it outside the main loop only once.

        projection = glm::perspective(45.0f, (float)WIDTH / (float)HEIGHT, 0.1f, 100.0f);

        // Get their uniform location and set then
        shader.setMat4("model", model);
        shader.setMat4("view", view.getView()); 
        shader.setMat4("projection", projection);
//        view.setView(glm::lookAt(view.getPosition(), view.getPosition() + view.getDirection(), view.getUp()));

        //draw alements

        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);

        // Swap the screen buffers
        glfwSwapBuffers(window);
    }
    // Properly de-allocate all resources once they've outlived their purpose
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    //glDeleteBuffers(1, &EBO);
    // Terminate GLFW, clearing any resources allocated by GLFW.
    glfwTerminate();
    return 0;
}

// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode){

    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);

    if(key >= 0 && key < 1024){
        if(action == GLFW_PRESS)
            keys[key] = true;
        else if(action == GLFW_RELEASE)
            keys[key] = false;
 }
    
}
    
bool firstMouse = true;

void mouse_callback(GLFWwindow* window, double xpos, double ypos){
    if (firstMouse)
    {
        view.setLastx(xpos);
        view.setLasty(ypos);
        firstMouse = false;
    }

    float xoffset = xpos - view.getLastx();
    float yoffset = view.getLasty() - ypos; // Reversed since y-coordinates go from bottom to left
    view.setLastx(xpos);
    view.setLasty(ypos);

    view.setSensitivity(0.05);
    GLfloat sensitivity = 0.05;	// Change this value to your liking
    xoffset *= view.getSensitivity();
    yoffset *= view.getSensitivity();

    view.setYaw(view.getYaw()+xoffset);
    view.setPitch(view.getPitch()+yoffset);

    // Make sure that when pitch is out of bounds, screen doesn't get flipped
    if (view.getPitch() > 89.0f)
        view.setPitch(89.0f);
    if (view.getPitch() < -89.0f)
        view.setPitch(-89.0f);

    glm::vec3 front;
    front.x = cos(glm::radians(view.getYaw())) * cos(glm::radians(view.getPitch()));
    front.y = sin(glm::radians(view.getPitch()));
    front.z = sin(glm::radians(view.getYaw())) * cos(glm::radians(view.getPitch()));
    view.setDirection(glm::normalize(front));
    view.updateCameraCoord();
}

void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}



